/*
 * Copyright (c) 2024 Airbyte, Inc., all rights reserved.
 */

package io.airbyte.cdk.load.test.util

import io.airbyte.cdk.load.data.AirbyteValue
import io.airbyte.cdk.load.data.ObjectValue
import io.airbyte.cdk.load.message.Meta.Change
import java.time.Instant
import java.util.UUID

/** A record that we expect to exist in the destination, whether raw _or_ final. */
data class OutputRecord(
    val rawId: UUID?,
    val extractedAt: Instant,
    val loadedAt: Instant?,
    val generationId: Long?,
    /**
     * strongly-typed map, e.g. ZonedDateTime for timestamp_with_timezone. this makes destination
     * test implementations easier. values can be null, b/c warehouse destinations with a JSON
     * column type can be either SQL null, or JSON null, and we want to distinguish between those.
     * Destinations _must_ filter out the airbyte_* fields from this map.
     */
    val data: ObjectValue,
    val airbyteMeta: Meta?,
) {
    /**
     * Much like [io.airbyte.cdk.message.DestinationRecord.Meta], but includes the [syncId] field
     * that we write to the destination.
     */
    data class Meta(
        val changes: List<Change> = listOf(),
        val syncId: Long? = null,
    )

    /** Utility constructor with easier types to write by hand */
    constructor(
        rawId: String,
        extractedAt: Long,
        loadedAt: Long?,
        generationId: Long?,
        data: Map<String, Any?>,
        airbyteMeta: Meta?,
    ) : this(
        rawId,
        extractedAt,
        loadedAt,
        generationId,
        ObjectValue.from(data),
        airbyteMeta,
    )

    constructor(
        rawId: String,
        extractedAt: Long,
        loadedAt: Long?,
        generationId: Long?,
        data: AirbyteValue,
        airbyteMeta: Meta?,
    ) : this(
        UUID.fromString(rawId),
        Instant.ofEpochMilli(extractedAt),
        loadedAt?.let { Instant.ofEpochMilli(it) },
        generationId,
        data as ObjectValue,
        airbyteMeta,
    )

    /**
     * Utility constructor for "expected records". [rawId] and [loadedAt] are generated by the
     * destination at runtime, so we don't have those when writing the test. Just generate arbitrary
     * values for them.
     */
    constructor(
        extractedAt: Long,
        generationId: Long?,
        data: Map<String, Any?>,
        airbyteMeta: Meta?,
    ) : this(
        null,
        Instant.ofEpochMilli(extractedAt),
        loadedAt = null,
        generationId,
        ObjectValue.from(data),
        airbyteMeta,
    )
}
